/*
NPlot - A charting library for .NET

Legend.cs
Copyright (C) 2003
Matt Howlett

Redistribution and use of NPlot or parts there-of in source and
binary forms, with or without modification, are permitted provided
that the following conditions are met:

1. Re-distributions in source form must retain at the head of each
   source file the above copyright notice, this list of conditions
   and the following disclaimer.

2. Any product ("the product") that makes use NPlot or parts 
   there-of must either:
  
    (a) allow any user of the product to obtain a complete machine-
        readable copy of the corresponding source code for the 
        product and the version of NPlot used for a charge no more
        than your cost of physically performing source distribution,
	on a medium customarily used for software interchange, or:

    (b) reproduce the following text in the documentation, about 
        box or other materials intended to be read by human users
        of the product that is provided to every human user of the
        product: 
   
              "This product includes software developed as 
              part of the NPlot library project available 
              from: http://www.nplot.com/" 

        The words "This product" may optionally be replace with 
        the actual name of the product.

------------------------------------------------------------------------

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;

namespace NPlot
{

	/// <summary>
	/// Legend functionality specific to Legends associated with a PlotSurface2D.
	/// </summary>
	public class Legend : LegendBase
	{

		/// <summary>
		/// Enumeration of possible Legend placements.
		/// </summary>
		public enum Placement
		{
			/// <summary>
			/// Inside the plot area.
			/// </summary>
			Inside = 0,
			/// <summary>
			/// Outside the plot area.
			/// </summary>
			Outside = 1
		}

		private int xOffset_;
		private int yOffset_;
		private PlotSurface2D.XAxisPosition xAttach_;
		private PlotSurface2D.YAxisPosition yAttach_;
		private Placement horizontalEdgePlacement_;
		private Placement verticalEdgePlacement_;
		private bool neverShiftAxes_;

		/// <summary>
		/// Whether or not the positions of the Axes may be shifted to make
		/// room for the Legend. 
		/// </summary>
		public bool NeverShiftAxes
		{
			get
			{
				return neverShiftAxes_;
			}
			set
			{
				neverShiftAxes_ = value;
			}
		}


		/// <summary>
		/// Offset from the chosen Y-Axis. TODO: better description.
		/// </summary>
		public int XOffset
		{
			get
			{
				return xOffset_;
			}
			set
			{
				xOffset_ = value;
			}
		}


		/// <summary>
		/// Offset from the X-Axis. TODO: better description.
		/// </summary>
		public int YOffset
		{
			get
			{
				return yOffset_;
			}
			set
			{
				yOffset_ = value;
			}
		}


		/// <summary>
		/// Whether or not to attach the legend on the inside of the top
		/// or bottom axis (which, is specified using the AttachTo method) or the 
		/// outside. 
		/// </summary>
		public Legend.Placement VerticalEdgePlacement
		{
			get
			{
				return verticalEdgePlacement_;
			}
			set
			{
				verticalEdgePlacement_ = value;
			}
		}

		
		/// <summary>
		/// Whether or not to attach the legend on the inside of the
		/// left or right axis (which, is specified using the AttachTo method) 
		/// or the outside.
		/// </summary>
		public Legend.Placement HorizontalEdgePlacement
		{
			get
			{
				return horizontalEdgePlacement_;
			}
			set
			{
				horizontalEdgePlacement_ = value;
			}
		}


		/// <summary>
		/// Specify the Axes to attach the legend to. 
		/// </summary>
		/// <param name="xa">Specify which horizontal axis the legend should be attached to.</param>
		/// <param name="ya">Specify which vertical axis the legend should be attached to.</param>
		public void AttachTo( PlotSurface2D.XAxisPosition xa, PlotSurface2D.YAxisPosition ya )
		{
			xAttach_ = xa;
			yAttach_ = ya; 
		}


		/// <summary>
		/// Default constructor.
		/// </summary>
		public Legend()
		{
			xAttach_ = PlotSurface2D.XAxisPosition.Top;
			yAttach_ = PlotSurface2D.YAxisPosition.Right;
			xOffset_ = 10;
			yOffset_ = 1;
			verticalEdgePlacement_ = Placement.Outside;
			horizontalEdgePlacement_ = Placement.Inside;
			neverShiftAxes_ = false;
		}


		/// <summary>
		/// Updates the PlotSurface2D axes to compensate for the legend.
		/// </summary>
		/// <param name="pXAxis1">the bottom x axis</param>
		/// <param name="pYAxis1">the left y axis</param>
		/// <param name="pXAxis2">the top x axis</param>
		/// <param name="pYAxis2">the right y axis</param>
		/// <param name="plots">list of plots.</param>
		/// <param name="scale">scale parameter (for text and other)</param>
		/// <param name="padding">padding around plot within bounds.</param>
		/// <param name="bounds">graphics surface bounds</param>
		/// <param name="position">legend position</param>
		public void UpdateAxesPositions( 
			PhysicalAxis pXAxis1,
			PhysicalAxis pYAxis1,
			PhysicalAxis pXAxis2,
			PhysicalAxis pYAxis2,
			ArrayList plots,
			float scale, int padding, Rectangle bounds,
			out Point position )
		{
		
			int leftIndent = 0;
			int rightIndent = 0;
			int bottomIndent = 0;
			int topIndent = 0;

			position = new Point(0,0);

			// now determine if legend should change any of these (legend should be fully 
			// visible at all times), and draw legend.

			Rectangle legendWidthHeight = this.GetBoundingBox( new Point(0,0), plots, scale );

			if (legendWidthHeight.Width > bounds.Width)
			{
				legendWidthHeight.Width = bounds.Width;
			}

			// (1) calculate legend position.

			// y

			position.Y = this.yOffset_;
			
			if ( this.xAttach_ == PlotSurface2D.XAxisPosition.Bottom )
			{
				position.Y += pYAxis1.PhysicalMin.Y;
				if ( this.horizontalEdgePlacement_ == Legend.Placement.Inside )
				{
					position.Y -= legendWidthHeight.Height;
				}
			}
			else
			{
				position.Y += pYAxis1.PhysicalMax.Y;
				if ( this.horizontalEdgePlacement_ == Legend.Placement.Outside )
				{
					position.Y -= legendWidthHeight.Height;
				}
			}
	
			// x

			position.X = this.xOffset_;
		
			if ( this.yAttach_ == PlotSurface2D.YAxisPosition.Left )
			{
				if ( this.verticalEdgePlacement_ == Legend.Placement.Outside ) 
				{
					position.X -= legendWidthHeight.Width;
				}
				position.X += pXAxis1.PhysicalMin.X;
			}
			else
			{
				if ( this.verticalEdgePlacement_ == Legend.Placement.Inside )
				{
					position.X -= legendWidthHeight.Width;
				}
				position.X += pXAxis1.PhysicalMax.X;
			}


			// determine update amounts for axes

			if ( !this.neverShiftAxes_ )
			{

				if ( position.X < padding )
				{
					int changeAmount = -position.X + padding;
					// only allow axes to move away from bounds.
					if ( changeAmount > 0 )					
					{
						leftIndent = changeAmount;
					}
					position.X += changeAmount;
				}

				if ( position.X + legendWidthHeight.Width > bounds.Right - padding )
				{
					int changeAmount = (position.X - bounds.Right + legendWidthHeight.Width + padding );
					// only allow axes to move away from bounds.
					if ( changeAmount > 0.0f ) 
					{
						rightIndent = changeAmount;
					}
					position.X -= changeAmount;
				}

				if ( position.Y < padding )
				{
					int changeAmount = -position.Y + padding;
					// only allow axes to move away from bounds.
					if ( changeAmount > 0.0f )					
					{
						topIndent = changeAmount;
					}
					position.Y += changeAmount;
				}

				if ( position.Y + legendWidthHeight.Height > bounds.Bottom - padding )
				{
					int changeAmount = (position.Y - bounds.Bottom + legendWidthHeight.Height + padding );
					// only allow axes to move away from bounds.
					if ( changeAmount > 0.0f ) 
					{
						bottomIndent = changeAmount;
					}
					position.Y -= changeAmount;
				}

				// update axes.

				pXAxis1.PhysicalMin = new Point( pXAxis1.PhysicalMin.X + leftIndent, pXAxis1.PhysicalMin.Y - bottomIndent );
				pXAxis1.PhysicalMax = new Point( pXAxis1.PhysicalMax.X - rightIndent, pXAxis1.PhysicalMax.Y - bottomIndent );
				pYAxis1.PhysicalMin = new Point( pYAxis1.PhysicalMin.X + leftIndent, pYAxis1.PhysicalMin.Y - bottomIndent );
				pYAxis1.PhysicalMax = new Point( pYAxis1.PhysicalMax.X + leftIndent, pYAxis1.PhysicalMax.Y + topIndent );

				pXAxis2.PhysicalMin = new Point( pXAxis2.PhysicalMin.X + leftIndent, pXAxis2.PhysicalMin.Y + topIndent );
				pXAxis2.PhysicalMax = new Point( pXAxis2.PhysicalMax.X - rightIndent, pXAxis2.PhysicalMax.Y + topIndent );
				pYAxis2.PhysicalMin = new Point( pYAxis2.PhysicalMin.X - rightIndent, pYAxis2.PhysicalMin.Y - bottomIndent );
				pYAxis2.PhysicalMax = new Point( pYAxis2.PhysicalMax.X - rightIndent, pYAxis2.PhysicalMax.Y + topIndent );
			
			}

		}

	}

}
